/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at:
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
 */
package org.olap4j.driver.xmla.proxy;

import org.olap4j.OlapException;
import org.olap4j.driver.xmla.*;
import org.olap4j.driver.xmla.cache.XmlaOlap4jCache;

import java.net.URL;
import java.net.URLConnection;
import java.util.Map;
import java.util.concurrent.Future;

/**
 * <p>
 * Abstract implementation of Proxy which adds a SOAP cache layer between the driver and its proxy implementations. It
 * can be configured via the setCache() method, as instructed in {@link XmlaOlap4jCachedProxy} interface.
 * 
 * <p>
 * It also offers helper methods to keep track of the HTTP cookies and sends them back to the server along with queries.
 * The useful methods are saveCookies(URL) and useCookies(URL).
 * 
 * @author Luc Boudreau
 */
abstract class XmlaOlap4jAbstractHttpProxy implements XmlaOlap4jCachedProxy {
	private final XmlaHelper helper = new XmlaHelper();

	/**
	 * Holds on to the cache implementation.
	 */
	private XmlaOlap4jCache cache = null;

	/**
	 * Holds on to the connection name which is associated to this proxy.
	 */
	private String cacheId;

	/**
	 * Keeps a link to the cookie manager instance.
	 */
	private XmlaOlap4jCookieManager cookieManager = null;

	/**
	 * Creates an XmlaOlap4jAbstractHttpProxy.
	 */
	protected XmlaOlap4jAbstractHttpProxy() {
	}

	/**
	 * Sends a request to a URL and returns the response.
	 * 
	 * @param url
	 *            Target URL
	 * @param request
	 *            Request string
	 * @return Response
	 */
	public abstract byte[] getResponse(XmlaOlap4jServerInfos serverInfos, String request)
			throws XmlaOlap4jProxyException;

	/**
	 * Submits a request for background execution.
	 * 
	 * @param url
	 *            URL
	 * @param request
	 *            Request
	 * @return Future object representing the submitted job
	 */
	public abstract Future<byte[]> getResponseViaSubmit(XmlaOlap4jServerInfos serverInfos, String request);

	/**
	 * Helper method to add cookies to a given connection.
	 * 
	 * @param urlConn
	 *            The url connection to which we want the cookies applied to.
	 */
	protected void useCookies(URLConnection urlConn) {
		// Initializes the cookie manager
		this.initCookieManager();
		// Saves the current cookies
		this.cookieManager.setCookies(urlConn);
	}

	/**
	 * Helper method to save cookies for later use.
	 * 
	 * @param urlConn
	 *            The url connection for which we want the cookies saved for later use.
	 */
	protected void saveCookies(URLConnection urlConn) {
		// Initializes the cookie manager
		this.initCookieManager();
		// Saves the current cookies
		this.cookieManager.storeCookies(urlConn);
	}

	@SuppressWarnings("unchecked")
	public void setCache(Map<String, String> config, Map<String, String> properties) throws OlapException {
		try {
			// Loads the cache class
			Class clazz = Class.forName(config.get(XmlaOlap4jDriver.Property.CACHE.name()));

			// Instantiates it
			this.cache = (XmlaOlap4jCache) clazz.newInstance();

			// Configures it
			this.cacheId = this.cache.setParameters(config, properties);
		} catch (ClassNotFoundException e) {
			throw helper.createException(
					"The specified cache class name could not be found : "
							+ config.get(XmlaOlap4jDriver.Property.CACHE.name()), e);
		} catch (InstantiationException e) {
			throw helper.createException(
					"The specified cache class name could not be instanciated : "
							+ config.get(XmlaOlap4jDriver.Property.CACHE.name()), e);
		} catch (IllegalAccessException e) {
			throw helper.createException(
					"An error was encountered while instanciating the cache : "
							+ config.get(XmlaOlap4jDriver.Property.CACHE.name()), e);
		} catch (IllegalArgumentException e) {
			throw helper.createException(
					"An error was encountered while instanciating the cache : "
							+ config.get(XmlaOlap4jDriver.Property.CACHE.name()), e);
		} catch (SecurityException e) {
			throw helper.createException(
					"An error was encountered while instanciating the cache : "
							+ config.get(XmlaOlap4jDriver.Property.CACHE.name()), e);
		}
	}

	// implement XmlaOlap4jProxy
	public byte[] get(XmlaOlap4jServerInfos serverInfos, String request) throws XmlaOlap4jProxyException {
		byte[] response = null;
		// Tries to fetch from cache
		try {
			response = getFromCache(serverInfos.getUrl(), request.getBytes(getEncodingCharsetName()));
			// Returns the cached value if found
			if (response != null) {
				return response;
			}
		} catch (Exception e) {
			throw new XmlaOlap4jProxyException("An exception was encountered while browsing the proxy cache.", e);
		}

		// Executes the query
		response = getResponse(serverInfos, request);

		try {
			// Adds to cache
			addToCache(serverInfos.getUrl(), request.getBytes(getEncodingCharsetName()), response);
			// Returns result
			return response;
		} catch (Exception e) {
			throw new XmlaOlap4jProxyException(
					"An exception was encountered while saving a response in the proxy cache.", e);
		}
	}

	/**
	 * Tries to fetch a cached response from the cache implementation.
	 * 
	 * @param url
	 *            The url used to send the request
	 * 
	 * @param request
	 *            The SOAP request to cache
	 * 
	 * @return either a response in a byte array or null if the response is not in cache
	 */
	private byte[] getFromCache(final URL url, final byte[] request) throws OlapException {
		return (this.cache != null) ? this.cache.get(this.cacheId, url, request) : null;
	}

	/**
	 * Caches an entry using the current cache implementation.
	 * 
	 * @param url
	 *            The URL from which originated the request
	 * @param request
	 *            The SOAP request to cache
	 * @param response
	 *            The SOAP response to cache
	 */
	private void addToCache(URL url, byte[] request, byte[] response) throws OlapException {
		if (this.cache != null) {
			this.cache.put(this.cacheId, url, request, response);
		}
	}

	// implement XmlaOlap4jProxy
	public Future<byte[]> submit(final XmlaOlap4jServerInfos serverInfos, final String request) {
		// The submit operation doesn't need to be cached yet, since it will
		// call the get operation to fetch the data later on. It will get cached
		// then.
		//
		// I still overridden the submit method in case we need some caching
		// done in the end. - Luc
		return getResponseViaSubmit(serverInfos, request);
	}

	/**
	 * Initializes the cookie manager. It is not initialized by default because some proxy implementation might not need
	 * this functionnality.
	 */
	private void initCookieManager() {
		if (this.cookieManager == null) {
			this.cookieManager = new XmlaOlap4jCookieManager();
		}
	}
}

// End XmlaOlap4jAbstractHttpProxy.java

